//适配器模式
const compileUtil = {
    getValue(expr, vm) {
        return expr.split('.').reduce((data, currentVal) => {
            return data[currentVal]
        }, vm.$data)
    },
    //设置值
    setValue(vm, expr, valu) {
        return expr.split('.').reduce((data, currentVal, index, arr) => {
            return data[currentVal] = valu
        }, vm.$data)
    },
    //获取新值 对{{a}}--{{b}} 这种格式进行处理
    getContentValue(expr, vm) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getValue(args[1], vm);
        })
    },
    text(node, expr, vm) {
        let value;
        if (expr.indexOf('{{') !== -1) {
            value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
                //绑定watcher从而更新视图
                new Watcher(vm, args[1], () => {
                    this.updater.textUpdater(node, this.getContentValue(expr, vm));
                })
                return this.getValue(args[1], vm);
            })
        } else {
            new Watcher(vm, expr, (newValue) => {
                this.updater.textUpdater(node, newValue);
            })
            value = this.getValue(expr, vm)
        }
        this.updater.textUpdater(node, value);
    },
    html(node, expr, vm) {
        // html处理 非常简单 直接取值 然后调用更新函数即可
        const val = this.getValue(expr, vm);
        //绑定watcher从而更新视图
        new Watcher(vm, expr, (newValue) => {
            this.updater.htmlUpdater(node, newValue);
        })
        this.updater.htmlUpdater(node, val);
    },
    model(node, expr, vm) {
        const value = this.getValue(expr,vm);
        // 订阅数据变化时 绑定更新函数 更新视图的变化

        // 数据==>视图
        new Watcher(vm, expr, (newVal) => {
            this.updater.modelUpdater(node, newVal);
        })
        // 视图==>数据
        node.addEventListener('input',(e)=>{
            // 设置值
            this.setValue(vm,expr,e.target.value);

        },false);
        this.updater.modelUpdater(node,value);
    },
    // 对事件进行处理
    on(node, expr, vm, eventName) {
        // 获取事件函数
        const fn = vm.$options.methods && vm.$options.methods[expr];
        // 添加事件 因为我们使用vue时 都不需要关心this的指向问题,这是因为源码的内部帮咱们处理了this的指向
        node.addEventListener(eventName, fn.bind(vm), false);
    },
    // 绑定属性 简单的属性 已经处理 类名样式的绑定有点复杂 因为对应的值可能是对象 也可能是数组 大家根据个人能力尝试写一下
    bind(node, expr, vm, attrName) {
        const attrVal = this.getVal(expr, vm);
        this.updater.attrUpdater(node, attrName, attrVal);
    },
    updater: {
        attrUpdater(node, attrName, attrVal) {
            node.setAttribute(attrName, attrVal);
        },
        modelUpdater(node, value) {
            node.value = value;
        },
        textUpdater(node, value) {
            node.textContent = value;

        },
        htmlUpdater(node, value) {
            node.innerHTML = value;
        }
    }

}

class MVue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        //如果这个根元素存在开始编译模板
        if (this.$el) {
            new Observer(this.$data);
            new Compile(this.$el, this)
        }
    }
}
class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        /*
           因为每次匹配到进行替换时,会导致页面的回流和重绘,影响页面的性能
           所以需要创建文档碎片来进行缓存,减少页面的回流和重绘
        */
        // 1.获取文档碎片对象
        const fragment = this.node2Fragment(this.el);
        // 2.编译模板
        this.compile(fragment)
        // 3.把子元素的所有内容添加到根元素中
        this.el.appendChild(fragment);
    }
    compile(fragment) {
        // 1.获取子节点
        const childNodes = fragment.childNodes;
        // 2、遍历子节点
        //原型NodeList,原型链上有forEach
        childNodes.forEach(node => {

            // 3、对子节点的类型进行不同的处理
            if (this.isElementNode(node)) {
                //编译元素节点
                this.compileElement(node);
            } else {
                //编译文本节点
                this.compileText(node);
            }
            // 4、判断是否还存在子节点 递归处理
            if (node.childNodes && node.childNodes.length) {
                this.compile(node)
            }
        })
    }
    //编译节点
    compileElement(node) {
        // 获取节点所有属性
        const attributes = node.attributes;
        [...attributes].forEach(attr => {
            /*
                name：v-text v-model v-on:click  @click
                value：user.name user.age msg
             */
            const { name, value } = attr;
            if (this.isDirective(name)) {
                //分割 v-text 拿到 text
                const [, directive] = name.split('-');
                // 分割 v-bind:src v-on:click 拿到 v-bind核src
                const [dirName, eventName] = directive.split(':');
                // 更新数据
                compileUtil[dirName] && compileUtil[dirName](node, value, this.vm, eventName);
                //删除当前元素带有v- 开头的属性
                node.removeAttribute('v-' + directive);
            } else if (this.isEventName(name)) {
                const [, eventName] = name.split('@');
                compileUtil['on'](node, value, this.vm, eventName)
            }
        })
    }
    //编译文本
    compileText(node) {
        const content = node.textContent;
        //正则匹配{{}}获取里面的内容
        if (/\{\{(.+?)\}\}/.test(content)) {
            // 处理文本节点
            compileUtil['text'](node, content, this.vm)
        }
    }
    //判断是否是@开头的事件名称
    isEventName(attrName) {
        return attrName.startsWith('@')
    }
    //判断是否是一个v-开头的指令
    isDirective(attrName) {
        return attrName.startsWith('v-')
    }
    //判断是否是一个元素节点
    isElementNode(node) {
        /*
          1  Element   元素结点
          2	 Attribute 属性节点
          3	 Text      文本结点
         */
        return node.nodeType === 1;
    }
    node2Fragment(el) {
        const fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild)
        }
        return fragment
    }

}